/* *
 *
 *  Copyright (c) 2019-2019 Highsoft AS
 *
 *  Boost module: stripped-down renderer for higher performance
 *
 *  License: highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
"use strict";
import H from "../../parts/Globals.js";
import U from "../../parts/Utilities.js";
var extend = U.extend,
  wrap = U.wrap;
import "../../parts/Series.js";
import butils from "./boost-utils.js";
import createAndAttachRenderer from "./boost-attach.js";
var addEvent = H.addEvent,
  fireEvent = H.fireEvent,
  Series = H.Series,
  seriesTypes = H.seriesTypes,
  noop = function () {},
  eachAsync = butils.eachAsync,
  pointDrawHandler = butils.pointDrawHandler,
  allocateIfNotSeriesBoosting = butils.allocateIfNotSeriesBoosting,
  renderIfNotSeriesBoosting = butils.renderIfNotSeriesBoosting,
  shouldForceChartSeriesBoosting = butils.shouldForceChartSeriesBoosting,
  index;
/* eslint-disable valid-jsdoc */
/**
 * Initialize the boot module.
 *
 * @private
 * @return {void}
 */
function init() {
  extend(Series.prototype, {
    /**
     * @private
     * @function Highcharts.Series#renderCanvas
     */
    renderCanvas: function () {
      var series = this,
        options = series.options || {},
        renderer = false,
        chart = series.chart,
        xAxis = this.xAxis,
        yAxis = this.yAxis,
        xData = options.xData || series.processedXData,
        yData = options.yData || series.processedYData,
        rawData = options.data,
        xExtremes = xAxis.getExtremes(),
        xMin = xExtremes.min,
        xMax = xExtremes.max,
        yExtremes = yAxis.getExtremes(),
        yMin = yExtremes.min,
        yMax = yExtremes.max,
        pointTaken = {},
        lastClientX,
        sampling = !!series.sampling,
        points,
        enableMouseTracking = options.enableMouseTracking !== false,
        threshold = options.threshold,
        yBottom = yAxis.getThreshold(threshold),
        isRange =
          series.pointArrayMap && series.pointArrayMap.join(",") === "low,high",
        isStacked = !!options.stacking,
        cropStart = series.cropStart || 0,
        requireSorting = series.requireSorting,
        useRaw = !xData,
        minVal,
        maxVal,
        minI,
        maxI,
        boostOptions,
        compareX = options.findNearestPointBy === "x",
        xDataFull =
          this.xData || this.options.xData || this.processedXData || false,
        addKDPoint = function (clientX, plotY, i) {
          // We need to do ceil on the clientX to make things
          // snap to pixel values. The renderer will frequently
          // draw stuff on "sub-pixels".
          clientX = Math.ceil(clientX);
          // Shaves off about 60ms compared to repeated concatenation
          index = compareX ? clientX : clientX + "," + plotY;
          // The k-d tree requires series points.
          // Reduce the amount of points, since the time to build the
          // tree increases exponentially.
          if (enableMouseTracking && !pointTaken[index]) {
            pointTaken[index] = true;
            if (chart.inverted) {
              clientX = xAxis.len - clientX;
              plotY = yAxis.len - plotY;
            }
            points.push({
              x: xDataFull ? xDataFull[cropStart + i] : false,
              clientX: clientX,
              plotX: clientX,
              plotY: plotY,
              i: cropStart + i,
            });
          }
        };
      // Get or create the renderer
      renderer = createAndAttachRenderer(chart, series);
      chart.isBoosting = true;
      boostOptions = renderer.settings;
      if (!this.visible) {
        return;
      }
      // If we are zooming out from SVG mode, destroy the graphics
      if (this.points || this.graph) {
        this.animate = null;
        this.destroyGraphics();
      }
      // If we're rendering per. series we should create the marker groups
      // as usual.
      if (!chart.isChartSeriesBoosting()) {
        // If all series were boosting, but are not anymore
        // restore private markerGroup
        if (this.markerGroup === chart.markerGroup) {
          this.markerGroup = void 0;
        }
        this.markerGroup = series.plotGroup(
          "markerGroup",
          "markers",
          true,
          1,
          chart.seriesGroup
        );
      } else {
        // If series has a private markeGroup, remove that
        // and use common markerGroup
        if (this.markerGroup && this.markerGroup !== chart.markerGroup) {
          this.markerGroup.destroy();
        }
        // Use a single group for the markers
        this.markerGroup = chart.markerGroup;
        // When switching from chart boosting mode, destroy redundant
        // series boosting targets
        if (this.renderTarget) {
          this.renderTarget = this.renderTarget.destroy();
        }
      }
      points = this.points = [];
      // Do not start building while drawing
      series.buildKDTree = noop;
      if (renderer) {
        allocateIfNotSeriesBoosting(renderer, this);
        renderer.pushSeries(series);
        // Perform the actual renderer if we're on series level
        renderIfNotSeriesBoosting(renderer, this, chart);
      }
      /**
       * This builds the KD-tree
       * @private
       */
      function processPoint(d, i) {
        var x,
          y,
          clientX,
          plotY,
          isNull,
          low = false,
          chartDestroyed = typeof chart.index === "undefined",
          isYInside = true;
        if (!chartDestroyed) {
          if (useRaw) {
            x = d[0];
            y = d[1];
          } else {
            x = d;
            y = yData[i];
          }
          // Resolve low and high for range series
          if (isRange) {
            if (useRaw) {
              y = d.slice(1, 3);
            }
            low = y[0];
            y = y[1];
          } else if (isStacked) {
            x = d.x;
            y = d.stackY;
            low = y - d.y;
          }
          isNull = y === null;
          // Optimize for scatter zooming
          if (!requireSorting) {
            isYInside = y >= yMin && y <= yMax;
          }
          if (!isNull && x >= xMin && x <= xMax && isYInside) {
            clientX = xAxis.toPixels(x, true);
            if (sampling) {
              if (typeof minI === "undefined" || clientX === lastClientX) {
                if (!isRange) {
                  low = y;
                }
                if (typeof maxI === "undefined" || y > maxVal) {
                  maxVal = y;
                  maxI = i;
                }
                if (typeof minI === "undefined" || low < minVal) {
                  minVal = low;
                  minI = i;
                }
              }
              // Add points and reset
              if (clientX !== lastClientX) {
                // maxI is number too:
                if (typeof minI !== "undefined") {
                  plotY = yAxis.toPixels(maxVal, true);
                  yBottom = yAxis.toPixels(minVal, true);
                  addKDPoint(clientX, plotY, maxI);
                  if (yBottom !== plotY) {
                    addKDPoint(clientX, yBottom, minI);
                  }
                }
                minI = maxI = void 0;
                lastClientX = clientX;
              }
            } else {
              plotY = Math.ceil(yAxis.toPixels(y, true));
              addKDPoint(clientX, plotY, i);
            }
          }
        }
        return !chartDestroyed;
      }
      /**
       * @private
       */
      function doneProcessing() {
        fireEvent(series, "renderedCanvas");
        // Go back to prototype, ready to build
        delete series.buildKDTree;
        series.buildKDTree();
        if (boostOptions.debug.timeKDTree) {
          console.timeEnd("kd tree building"); // eslint-disable-line no-console
        }
      }
      // Loop over the points to build the k-d tree - skip this if
      // exporting
      if (!chart.renderer.forExport) {
        if (boostOptions.debug.timeKDTree) {
          console.time("kd tree building"); // eslint-disable-line no-console
        }
        eachAsync(
          isStacked ? series.data : xData || rawData,
          processPoint,
          doneProcessing
        );
      }
    },
  });
  /*
   * We need to handle heatmaps separatly, since we can't perform the
   * size/color calculations in the shader easily.
   *
   * This likely needs future optimization.
   */
  ["heatmap", "treemap"].forEach(function (t) {
    if (seriesTypes[t]) {
      wrap(seriesTypes[t].prototype, "drawPoints", pointDrawHandler);
    }
  });
  /* eslint-disable no-invalid-this */
  if (seriesTypes.bubble) {
    // By default, the bubble series does not use the KD-tree, so force it
    // to.
    delete seriesTypes.bubble.prototype.buildKDTree;
    // seriesTypes.bubble.prototype.directTouch = false;
    // Needed for markers to work correctly
    wrap(seriesTypes.bubble.prototype, "markerAttribs", function (proceed) {
      if (this.isSeriesBoosting) {
        return false;
      }
      return proceed.apply(this, [].slice.call(arguments, 1));
    });
  }
  seriesTypes.scatter.prototype.fill = true;
  extend(seriesTypes.area.prototype, {
    fill: true,
    fillOpacity: true,
    sampling: true,
  });
  extend(seriesTypes.column.prototype, {
    fill: true,
    sampling: true,
  });
  // Take care of the canvas blitting
  H.Chart.prototype.callbacks.push(function (chart) {
    /**
     * Convert chart-level canvas to image.
     * @private
     */
    function canvasToSVG() {
      if (chart.ogl && chart.isChartSeriesBoosting()) {
        chart.ogl.render(chart);
      }
    }
    /**
     * Clear chart-level canvas.
     * @private
     */
    function preRender() {
      // Reset force state
      chart.boostForceChartBoost = void 0;
      chart.boostForceChartBoost = shouldForceChartSeriesBoosting(chart);
      chart.isBoosting = false;
      if (!chart.isChartSeriesBoosting() && chart.didBoost) {
        chart.didBoost = false;
      }
      // Clear the canvas
      if (chart.boostClear) {
        chart.boostClear();
      }
      if (chart.canvas && chart.ogl && chart.isChartSeriesBoosting()) {
        chart.didBoost = true;
        // Allocate
        chart.ogl.allocateBuffer(chart);
      }
      // see #6518 + #6739
      if (
        chart.markerGroup &&
        chart.xAxis &&
        chart.xAxis.length > 0 &&
        chart.yAxis &&
        chart.yAxis.length > 0
      ) {
        chart.markerGroup.translate(chart.xAxis[0].pos, chart.yAxis[0].pos);
      }
    }
    addEvent(chart, "predraw", preRender);
    addEvent(chart, "render", canvasToSVG);
    // addEvent(chart, 'zoom', function () {
    //     chart.boostForceChartBoost =
    //         shouldForceChartSeriesBoosting(chart);
    // });
  });
  /* eslint-enable no-invalid-this */
}
export default init;
